#include "utils.h"
#include "http_cli.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "nng.h"
#include "supplemental/http/http.h"

#ifdef _OS_FREEBSD
#include <limits.h>
#endif

#define __http_req_max_body_siz 1 * 1024 * 1024 //1MB

static Error *http_req(struct HttpReq *req) __mustuse;

__pub struct HttpCli httpcli = {
    .req = http_req,
};

struct HttpCliFlow{
    nng_url *url;
    nng_http_req *req;
    nng_http_res *res;

    nng_aio *        aio;
    nng_http_client *cli;
    nng_http_conn *  conn;
};

static void
HttpCliFlow_clean(struct HttpCliFlow *c){
    if(nil == c) return;

    if(nil != c->url) nng_url_free(c->url);
    if(nil != c->req) nng_http_req_free(c->req);
    if(nil != c->res) nng_http_res_free(c->res);

    if(nil != c->aio) nng_aio_free(c->aio);
    if(nil != c->conn) nng_http_conn_close(c->conn);
    if(nil != c->cli) nng_http_client_free(c->cli);
}

//@param urlstr[in]:
//@param method[in]:GET or POST
//@param req[in, out]: ** req->body: in and out if POST, only out if GET **
static Error *
http_req(struct HttpReq *req){
    __return_err_if_any_nil(req&&req->url&&req->method);
    __drop(HttpCliFlow_clean)
    struct HttpCliFlow cl = { nil, nil, nil, nil, nil, nil };
    _i rv = 0;

    req->body.drop = utils.non_drop;
    req->status_code = -1;

    if((0 != (rv = nng_url_parse(&cl.url, req->url)))
            || 0 != ((rv = nng_http_req_alloc(&cl.req, cl.url)))
            || 0 != ((rv = nng_http_res_alloc(&cl.res)))
            || 0 != (rv = nng_http_req_set_method(cl.req, req->method))){
        return __err_new(rv, nng_strerror(rv), nil);
    }

    for(_i i = 0; i < (req->nheaders_x_2 - (req->nheaders_x_2 % 2)); i += 2){
        if((0 != (rv = nng_http_req_add_header(cl.req, req->headers[i], req->headers[i + 1])))){
            return __err_new(rv, nng_strerror(rv), nil);
        }
    }

    if (0 != ((rv = nng_aio_alloc(&cl.aio, nil, nil)))
            || (0 != (rv = nng_http_client_alloc(&cl.cli, cl.url)))){
        return __err_new(rv, nng_strerror(rv), nil);
    }

    nng_http_client_connect(cl.cli, cl.aio);
    nng_aio_wait(cl.aio);
    if (0 != (rv = nng_aio_result(cl.aio))){
        return __err_new(rv, nng_strerror(rv), nil);
    }

    cl.conn = nng_aio_get_output(cl.aio, 0);

    if(nil != req->body.data){
        if(__http_req_max_body_siz < req->body.dsiz){
            return __err_new(-1, "data size too big", nil);
        }

        if(0 != (rv = nng_http_req_copy_data(cl.req, req->body.data, req->body.dsiz))){
            return __err_new(rv, nng_strerror(rv), nil);
        }
    }

    nng_http_conn_write_req(cl.conn, cl.req, cl.aio);
    nng_aio_wait(cl.aio);
    if (0 != (rv = nng_aio_result(cl.aio))){
        return __err_new(rv, nng_strerror(rv), nil);
    }

    nng_http_conn_read_res(cl.conn, cl.res, cl.aio);
    nng_aio_wait(cl.aio);
    if (0 != (rv = nng_aio_result(cl.aio))) {
        return __err_new(rv, nng_strerror(rv), nil);
    }

    req->status_code = nng_http_res_get_status(cl.res);

    req->body.data = nil;
    req->body.dsiz = 0;

    const char *ptr = nng_http_res_get_header(cl.res, "Content-Length");
    if(nil != ptr){
        req->body.dsiz = strtol(ptr, nil, 10);
        if (0 < req->body.dsiz) {
            nng_iov iov;
            if(nil == (req->body.data = nng_alloc(req->body.dsiz))){
                return __err_new(-1, ptr, nil);
            } else {
                req->body.drop = utils.nng_drop;
            }

            iov.iov_buf = req->body.data;
            iov.iov_len = req->body.dsiz;
            nng_aio_set_iov(cl.aio, 1, &iov);
            nng_http_conn_read_all(cl.conn, cl.aio);
            nng_aio_wait(cl.aio);
            if (0 != (rv = nng_aio_result(cl.aio))) {
                nng_free(req->body.data, req->body.dsiz);
                req->body.data = nil;
                req->body.dsiz = 0;
                req->body.drop = utils.non_drop;
                return __err_new(rv, nng_strerror(rv), nil);
            }
        }
    }

    return nil;
}
